package com.caipos.ruleNode.profile;

import com.caipos.actor.api.*;
import com.caipos.actor.common.data.DeviceProfile;
import com.caipos.actor.common.data.EntityType;
import com.caipos.actor.common.data.id.CustomerId;
import com.caipos.actor.common.data.id.DeviceId;
import com.caipos.actor.common.data.id.DeviceProfileId;
import com.caipos.actor.common.data.msg.KbMsgType;
import com.caipos.actor.common.data.rule.RuleNodeState;
import com.caipos.actor.common.msg.KbMsg;
import com.caipos.actor.common.msg.KbMsgMetaData;
import com.caipos.service.DeviceState;
import com.caipos.util.JacksonUtil;
import com.caipos.util.KbNodeUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

@Slf4j
@RuleNode(
        type = ComponentType.ACTION,
        name = "device profile",
        customRelations = true,
        relationTypes = {"Alarm Created", "Alarm Updated", "Alarm Severity Updated", "Alarm Cleared", "Success", "Failure"},
        version = 1,
        configClazz = KbDeviceProfileNodeConfiguration.class,
        nodeDescription = "Process device messages based on device profile settings",
        nodeDetails = "Create and clear alarms based on alarm rules defined in device profile. The output relation type is either " +
                "'Alarm Created', 'Alarm Updated', 'Alarm Severity Updated' and 'Alarm Cleared' or simply 'Success' if no alarms were affected.",
        configDirective = "tbActionNodeDeviceProfileConfig"
)
public class KbDeviceProfileNode implements KbNode {

    private KbDeviceProfileNodeConfiguration config;
    private RuleEngineDeviceProfileCache cache;
    private KbContext ctx;
    private final Map<DeviceId, DeviceState> deviceStates = new ConcurrentHashMap<>();

    @Override
    public void init(KbContext ctx, KbNodeConfiguration configuration) throws KbNodeException {
        this.config = KbNodeUtils.convert(configuration, KbDeviceProfileNodeConfiguration.class);
//        this.cache = ctx.getDeviceProfileCache();
        this.ctx = ctx;
        scheduleAlarmHarvesting(ctx, null);
//        ctx.addDeviceProfileListeners(this::onProfileUpdate, this::onDeviceUpdate);
        initAlarmRuleState(false);
    }

    private void initAlarmRuleState(boolean printNewlyAddedDeviceStates) {
        if (config.isFetchAlarmRulesStateOnStart()) {
            log.info("[{}] Fetching alarm rule state", ctx.getSelfId());
            int fetchCount = 0;
//            PageLink pageLink = new PageLink(1024);
//            while (true) {
//                PageData<RuleNodeState> states = ctx.findRuleNodeStates(pageLink);
//                if (!states.getData().isEmpty()) {
//                    for (RuleNodeState rns : states.getData()) {
//                        fetchCount++;
//                        if (rns.getEntityId().getEntityType().equals(EntityType.DEVICE) && ctx.isLocalEntity(rns.getEntityId())) {
//                            getOrCreateDeviceState(ctx, new DeviceId(rns.getEntityId().getId()), rns, printNewlyAddedDeviceStates);
//                        }
//                    }
//                }
//                if (!states.hasNext()) {
//                    break;
//                } else {
//                    pageLink = pageLink.nextPageLink();
//                }
//            }
            log.info("[{}] Fetched alarm rule state for {} entities", ctx.getSelfId(), fetchCount);
        }
        if (!config.isPersistAlarmRulesState() && ctx.isLocalEntity(ctx.getSelfId())) {
            log.debug("[{}] Going to cleanup rule node states", ctx.getSelfId());
            ctx.clearRuleNodeStates();
        }
    }

    @Override
    public void onMsg(KbContext ctx, KbMsg msg) throws ExecutionException, InterruptedException {
        EntityType originatorType = msg.getOriginator().getEntityType();
        if (msg.isTypeOf(KbMsgType.DEVICE_PROFILE_PERIODIC_SELF_MSG)) {
            scheduleAlarmHarvesting(ctx, msg);
            harvestAlarms(ctx, System.currentTimeMillis());
        } else if (msg.isTypeOf(KbMsgType.DEVICE_PROFILE_UPDATE_SELF_MSG)) {
            updateProfile(ctx, new DeviceProfileId(msg.getData()));
        } else if (msg.isTypeOf(KbMsgType.DEVICE_UPDATE_SELF_MSG)) {
            JsonNode data = JacksonUtil.toJsonNode(msg.getData());
            DeviceId deviceId = new DeviceId(data.get("deviceId").toString());
            if (data.has("profileId")) {
                invalidateDeviceProfileCache(deviceId, new DeviceProfileId(data.get("deviceProfileId").toString()));
            } else {
                removeDeviceState(deviceId);
            }
        } else {
            if (EntityType.DEVICE.equals(originatorType)) {
                DeviceId deviceId = new DeviceId(msg.getOriginator().getId());
                if (msg.isTypeOf(KbMsgType.ENTITY_UPDATED)) {
                    invalidateDeviceProfileCache(deviceId, msg.getData());
                    ctx.tellSuccess(msg);
                } else if (msg.isTypeOf(KbMsgType.ENTITY_DELETED)) {
                    removeDeviceState(deviceId);
                    ctx.tellSuccess(msg);
                } else {
                    DeviceState deviceState = getOrCreateDeviceState(ctx, deviceId, null, false);
                    if (deviceState != null) {
//                        deviceState.process(ctx, msg);
                    } else {
                        log.info("Device was not found! Most probably device [" + deviceId + "] has been removed from the database. Acknowledging msg.");
                        ctx.ack(msg);
                    }
                }
            } else {
                ctx.tellSuccess(msg);
            }
        }
    }

    @Override
    public void destroy() {
        ctx.removeListeners();
        deviceStates.clear();
    }

    protected DeviceState getOrCreateDeviceState(KbContext ctx, DeviceId deviceId, RuleNodeState rns, boolean printNewlyAddedDeviceStates) {
        DeviceState deviceState = deviceStates.get(deviceId);
        if (deviceState == null) {
            DeviceProfile deviceProfile = cache.get(ctx.getTenantId(), deviceId);
            if (deviceProfile != null) {
//                deviceState = new DeviceState(ctx, config, deviceId, new ProfileState(deviceProfile), rns);
//                deviceStates.put(deviceId, deviceState);
//                if (printNewlyAddedDeviceStates) {
//                    log.info("[{}][{}] Device [{}] was added during PartitionChangeMsg", ctx.getTenantId(), ctx.getSelfId(), deviceId);
//                }
            }
        }
        return deviceState;
    }

    protected void scheduleAlarmHarvesting(KbContext ctx, KbMsg msg) {
        CustomerId customerId = msg != null ? msg.getCustomerId() : null;
        KbMsg periodicCheck = KbMsg.newMsg()
                .type(KbMsgType.DEVICE_PROFILE_PERIODIC_SELF_MSG)
                .originator(ctx.getTenantId())
                .customerId(customerId)
                .copyMetaData(KbMsgMetaData.EMPTY)
                .data(KbMsg.EMPTY_JSON_OBJECT)
                .build();
        ctx.tellSelf(periodicCheck, TimeUnit.MINUTES.toMillis(1));
    }

    protected void harvestAlarms(KbContext ctx, long ts) throws ExecutionException, InterruptedException {
//        for (DeviceState state : deviceStates.values()) {
//            state.harvestAlarms(ctx, ts);
//        }
    }

    protected void updateProfile(KbContext ctx, DeviceProfileId deviceProfileId) throws ExecutionException, InterruptedException {
        DeviceProfile deviceProfile = cache.get(ctx.getTenantId(), deviceProfileId);
        if (deviceProfile != null) {
            log.debug("[{}] Received device profile update notification: {}", ctx.getSelfId(), deviceProfile);
//            for (DeviceState state : deviceStates.values()) {
//                if (deviceProfile.getId().equals(state.getProfileId())) {
//                    state.updateProfile(ctx, deviceProfile);
//                }
//            }
        } else {
            log.debug("[{}] Received stale profile update notification: [{}]", ctx.getSelfId(), deviceProfileId);
        }
    }

    protected void onProfileUpdate(DeviceProfile profile) {
        ctx.tellSelf(KbMsg.newMsg()
                .type(KbMsgType.DEVICE_PROFILE_UPDATE_SELF_MSG)
                .originator(ctx.getTenantId())
                .copyMetaData(KbMsgMetaData.EMPTY)
                .data(profile.getId().getId().toString())
                .build(), 0L);
    }

    private void onDeviceUpdate(DeviceId deviceId, DeviceProfile deviceProfile) {
        ObjectNode msgData = JacksonUtil.newObjectNode();
        msgData.put("deviceId", deviceId.getId().toString());
        if (deviceProfile != null) {
            msgData.put("deviceProfileId", deviceProfile.getId().getId().toString());
        }
        ctx.tellSelf(KbMsg.newMsg()
                .type(KbMsgType.DEVICE_UPDATE_SELF_MSG)
                .originator(ctx.getTenantId())
                .copyMetaData(KbMsgMetaData.EMPTY)
                .data(JacksonUtil.toString(msgData))
                .build(), 0L);
    }

    protected void invalidateDeviceProfileCache(DeviceId deviceId, String deviceJson) {
//        DeviceState deviceState = deviceStates.get(deviceId);
//        if (deviceState != null) {
//            DeviceProfileId currentProfileId = deviceState.getProfileId();
//            try {
//                Device device = JacksonUtil.fromString(deviceJson, Device.class);
//                if (!currentProfileId.equals(device.getDeviceProfileId())) {
//                    removeDeviceState(deviceId);
//                }
//            } catch (IllegalArgumentException e) {
//                log.debug("[{}] Received device update notification with non-device msg body: [{}]", ctx.getSelfId(), deviceId, e);
//            }
//        }
    }

    protected void invalidateDeviceProfileCache(DeviceId deviceId, DeviceProfileId deviceProfileId) {
//        DeviceState deviceState = deviceStates.get(deviceId);
//        if (deviceState != null) {
//            if (!deviceState.getProfileId().equals(deviceProfileId)) {
//                removeDeviceState(deviceId);
//            }
//        }
    }

    private void removeDeviceState(DeviceId deviceId) {
//        DeviceState state = deviceStates.remove(deviceId);
//        if (config.isPersistAlarmRulesState() && (state != null || !config.isFetchAlarmRulesStateOnStart())) {
//            ctx.removeRuleNodeStateForEntity(deviceId);
//        }
    }

}
